#ifndef LIBRARY_FUNCTIONS_CPP
#define LIBRARY_FUNCTIONS_CPP

#include "GOAL.h"
#include "stdafx.h"
#include "Library_Functions.h"

const std::string GOAL::Library::const_lookups[] = {
    "GOAL.Primitives.Char",
    "GOAL.Primitives.String",
    "GOAL.Primitives.Integer",
    "GOAL.Primitives.Short",
    "GOAL.Primitives.Long",
    "GOAL.Primitives.Float",
    "GOAL.Primitives.Double"
  };

/// this needs to reflect the list above. It is used by the 
/// primitiveCreate function. Please update it accordingly if
/// the list is ever changed
const int GOAL::Library::Primitives::Integer = 2;

bool 
GOAL::objectExists (const std::string& path, GOAL::RootFolder& root)
{
  Udm::Object object = getObjectByPath (path,root);

  return object != root;
}

Udm::Object 
GOAL::getObjectByPath (const std::string& path, RootFolder& current)
{
  Udm::Object returnValue = current;

  std::set <GOAL::ModuleAdapter> modules = current.ModuleAdapter_kind_children ();
  std::set <GOAL::ModuleAdapter>::iterator i = modules.begin ();

  //AfxMessageBox(current.getPath ("").c_str ());

  for (; i != modules.end (); ++i)
    {
      returnValue = getObjectByPath (path,*i,current);
      if (returnValue != current)
      {
        return returnValue;
      }
    }
  return returnValue;
}

Udm::Object 
GOAL::getObjectByPath (const std::string& path, 
      GOAL::ModuleAdapter& current, Udm::Object& path_relative_to)
{
  Udm::Object returnValue = path_relative_to;

  std::string curPath = current.getPath (".",false,false,"name",true);
  std::string rootPath = path_relative_to.getPath (".",false,false,"name",true);

  curPath = curPath.erase (0, rootPath.length () + 1);

  //AfxMessageBox (curPath.c_str ());

  if (path.find (curPath) != std::string::npos)
    {
      if (path == curPath)
        {
          //AfxMessageBox ("M: Found!");
          return current;
        }
    }

  // search through the modules for a path match

  std::set <GOAL::Module> modules = current.Module_kind_children ();
  for (std::set <GOAL::Module>::iterator i = modules.begin ();
       i != modules.end ();
       ++i)
    {
      returnValue = getObjectByPath (path,*i, path_relative_to);
      if (returnValue != path_relative_to)
      {
        return returnValue;
      }
    }

  // search through the classes for a path match

  std::set <GOAL::Class> classes = current.Class_kind_children ();
  for (std::set <GOAL::Class>::iterator i = classes.begin ();
       i != classes.end ();
       ++i)
    {
      std::string iPath ((*i).getPath (".",false,false,"name",true));
      iPath = iPath.erase (0, rootPath.length () + 1);

      //AfxMessageBox (iPath.c_str ());
      if (path.find (iPath) != std::string::npos)
        {
          //AfxMessageBox ("C: Found!");
          return *i;
        }
    }

  return returnValue;
}

std::string
GOAL::getParentPath (const std::string& path)
{
  std::string::size_type last = path.find_last_of (".");

  return path.substr (0, last);
}

std::string
GOAL::getChildName (const std::string& path)
{
  std::string::size_type last = path.find_last_of (".");

  return path.substr (last+1);
}

GOAL::Library::Library ()
{
  for (int i = 0; i < NUM_LIBRARIES; ++i)
    {
      int x = 10;
      int y = 120 * i;

      string position ("(");
      char buff[256];

      itoa (x,buff,10);

      position += buff;
      position += ",";
      
      itoa (y,buff,10);

      position += buff;
      position += ")";

      positions_[const_lookups[i]] = position;
    }
}

GOAL::Library::~Library ()
{

}

void
GOAL::Library::read (const std::string& name, Udm::SmartDataNetwork& library)
{
//  Udm::SmartDataNetwork library (GOAL::diagram);

  std::string library_file (getenv("GOAL_ROOT"));

  // library directory
  library_file += "/library";

  // primitives
  library_file += "/" + name + ".mga";

	AfxMessageBox(library_file.c_str ());

  library.OpenExisting (library_file.c_str ());
}

Udm::Object 
GOAL::Library::createPrimitive (const std::string& primitive, 
      GOAL::RootFolder& root)
{
  // check for accidental second creation
  Udm::Object object = GOAL::getObjectByPath (primitive, root);
  if (object != root)
    return object;

  // create parent if it doesn't exist
  GOAL::ModuleAdapter parent = GOAL::ModuleAdapter::Cast (
                                 getParent (primitive, root));

  // create the primitive
  GOAL::Class newClass = GOAL::Class::Create (parent);
  newClass.name () = GOAL::getChildName (primitive);
  newClass.position () = positions_[primitive.c_str ()];

  // create the integer class if one isn't in the model
  GOAL::Class integer = GOAL::Class::Cast (getInteger (root));

  // create the primitive functions required
  createCopyConstructor (newClass);
  createSizeFunction (newClass, integer);

  if ("String" == GOAL::getChildName (primitive))
    createLengthFunction (newClass, integer);

  return newClass;
}

Udm::Object 
GOAL::Library::createSizeFunction (GOAL::Class& newClass, GOAL::Class& integer)
{
  // create size function

  GOAL::Function sizeFunction = GOAL::Function::Create (newClass);
  sizeFunction.name () = "size";
  sizeFunction.position () = "(0,120)";

  GOAL::Return ret = GOAL::Return::Create (sizeFunction);
  ret.name () = "return";
  GOAL::ClassRef thisClassRef = GOAL::ClassRef::Create (ret);
  thisClassRef.name () = "IntegerRef";
  thisClassRef.ref () = integer;  

  return sizeFunction;
}

Udm::Object 
GOAL::Library::createLengthFunction (GOAL::Class& newClass, GOAL::Class& integer)
{
  // create size function

  GOAL::Function function = GOAL::Function::Create (newClass);
  function.name () = "length";
  function.position () = "(160,120)";

  GOAL::Return ret = GOAL::Return::Create (function);
  ret.name () = "return";
  GOAL::ClassRef thisClassRef = GOAL::ClassRef::Create (ret);
  thisClassRef.name () = "IntegerRef";
  thisClassRef.ref () = integer;  

  return function;
}

Udm::Object 
GOAL::Library::createCopyConstructor (GOAL::Class& newClass)
{
  // create constructor
  GOAL::Function constructor = GOAL::Function::Create (newClass);
  constructor.name () = newClass.name ();
  constructor.position () = "(0,0)";

  // create the copy constructor argument
  GOAL::Argument arg = GOAL::Argument::Create (constructor);
  arg.name () = "arg";

  // create the ref to this class
  GOAL::ClassRef thisClassRef = GOAL::ClassRef::Create (arg);
  thisClassRef.name () = newClass.name ().operator std::string () + "Ref";
  thisClassRef.ref () = newClass;

  // create the return value
  GOAL::Return ret = GOAL::Return::Create (constructor);
  ret.name () = "return";

  thisClassRef = GOAL::ClassRef::Create (ret);
  thisClassRef.name () = newClass.name ().operator std::string () + "Ref";
  thisClassRef.ref () = newClass;

  return constructor;
}

Udm::Object  
GOAL::Library::getParent (const std::string& primitive, 
      GOAL::RootFolder& root)
{
  Udm::Object proj_primitives = GOAL::getObjectByPath (
    GOAL::getParentPath (primitive),root);

  // create the parent module if one isn't in the model
  if (proj_primitives == root)
  {
    proj_primitives = GOAL::ModuleAdapter::Create (root);

    GOAL::ModuleAdapter module = GOAL::ModuleAdapter::Cast (
      proj_primitives);

    module.name () = GOAL::getParentPath (primitive);
    proj_primitives = module;
  }

  return proj_primitives;
}

Udm::Object  
GOAL::Library::getInteger (GOAL::RootFolder& root)
{
  if (!objectExists (const_lookups[GOAL::Library::Primitives::Integer],root))
  {
    createPrimitive (
      const_lookups[GOAL::Library::Primitives::Integer], root);
  }

  return GOAL::getObjectByPath (
     const_lookups[GOAL::Library::Primitives::Integer],root);
}


#endif